home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / transmission / web / javascript / transmission.js < prev    next >
Encoding:
JavaScript  |  2009-03-30  |  40.3 KB  |  1,388 lines

  1. /*
  2.  *    Copyright ¬© Dave Perrett and Malcolm Jarvis
  3.  *    This code is licensed under the GPL version 2.
  4.  *    For details, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  5.  *
  6.  * Class Transmission
  7.  */
  8.  
  9. function Transmission(){
  10.     this.initialize();
  11.  
  12. Transmission.prototype =
  13. {
  14.     /*--------------------------------------------
  15.      * 
  16.      *  C O N S T R U C T O R
  17.      * 
  18.      *--------------------------------------------*/
  19.  
  20.     initialize: function()
  21.     {
  22.         // IE specific fixes here
  23.         if ($.browser.msie) {
  24.             try {
  25.               document.execCommand("BackgroundImageCache", false, true);
  26.             } catch(err) {}
  27.             $('head').append('<link media="screen" href="./stylesheets/common.css" type="text/css" rel="stylesheet" />');
  28.             $('head').append('<link media="screen" href="./stylesheets/ie'+$.browser.version.substr(0,1)+'.css" type="text/css" rel="stylesheet" />');
  29.             $('.dialog_container').css('height',$(window).height()+'px');
  30.         }
  31.         
  32.         // Initialize the helper classes
  33.         this.remote = new TransmissionRemote(this);
  34.  
  35.         // Initialize the implementation fields
  36.         this._current_search         = '';
  37.         this._torrents               = [ ];
  38.         this._rows                   = [ ];
  39.     
  40.         // Initialize the clutch preferences
  41.         Prefs.getClutchPrefs( this );
  42.         
  43.         this.preloadImages();
  44.         
  45.         // Set up user events
  46.         $('#pause_all_link').bind('click', this.stopAllClicked );
  47.         $('#resume_all_link').bind('click', this.startAllClicked);
  48.         $('#pause_selected_link').bind('click', this.stopSelectedClicked );
  49.         $('#resume_selected_link').bind('click', this.startSelectedClicked);
  50.         $('#remove_link').bind('click',  this.removeClicked);
  51.         $('#removedata_link').bind('click',  this.removeDataClicked);
  52.         $('#filter_all_link').parent().bind('click', this.showAllClicked);
  53.         $('#filter_downloading_link').parent().bind('click', this.showDownloadingClicked);
  54.         $('#filter_seeding_link').parent().bind('click', this.showSeedingClicked);
  55.         $('#filter_paused_link').parent().bind('click', this.showPausedClicked);
  56.         $('#prefs_save_button').bind('click', this.savePrefsClicked);
  57.         $('#prefs_cancel_button').bind('click', this.cancelPrefsClicked);
  58.         $('#inspector_tab_info').bind('click', this.inspectorTabClicked);
  59.         $('#inspector_tab_activity').bind('click', this.inspectorTabClicked);
  60.         if (iPhone) {
  61.             $('#torrent_inspector').bind('click', this.hideInspector);
  62.             $('#preferences_link').bind('click', this.releaseClutchPreferencesButton);
  63.         } else {
  64.             $(document).bind('keydown',  this.keyDown);
  65.             $('#torrent_container').bind('click', this.deselectAll);
  66.             $('#open_link').bind('click', this.openTorrentClicked);
  67.             $('#filter_toggle_link').bind('click', this.toggleFilterClicked);
  68.             $('#inspector_link').bind('click', this.toggleInspectorClicked);
  69.             $('#upload_confirm_button').bind('click', this.confirmUploadClicked);
  70.             $('#upload_cancel_button').bind('click', this.cancelUploadClicked);
  71.         
  72.             this.setupSearchBox();
  73.             this.createContextMenu();
  74.             this.createSettingsMenu();
  75.         }
  76.         
  77.         // Setup the preference box
  78.         this.setupPrefConstraints();
  79.         
  80.         // Setup the prefs gui
  81.         this.initializeSettings( );
  82.         
  83.         // Get preferences & torrents from the daemon
  84.         this.remote.loadDaemonPrefs( );
  85.         this.remote.loadTorrents( );
  86.         this.togglePeriodicRefresh( true );
  87.     },
  88.  
  89.     preloadImages: function() {
  90.         if (iPhone) {
  91.             this.loadImages(
  92.                 'images/buttons/info_activity.png',
  93.                 'images/buttons/info_general.png',
  94.                 'images/buttons/toolbar_buttons.png',
  95.                 'images/graphics/filter_bar.png',
  96.                 'images/graphics/iphone_chrome.png',
  97.                 'images/graphics/logo.png',
  98.                 'images/progress/progress.png'
  99.             );
  100.         } else {
  101.             this.loadImages(
  102.                 'images/buttons/info_activity.png',
  103.                 'images/buttons/info_general.png',
  104.                 'images/buttons/tab_backgrounds.png',
  105.                 'images/buttons/toolbar_buttons.png',
  106.                 'images/buttons/torrent_buttons.png',
  107.                 'images/graphics/chrome.png',
  108.                 'images/graphics/filter_bar.png',
  109.                 'images/graphics/logo.png',
  110.                 'images/graphics/transfer_arrows.png',
  111.                 'images/progress/progress.png'
  112.             );
  113.         }
  114.     },
  115.     loadImages: function() {
  116.         for(var i = 0; i<arguments.length; i++) {
  117.             jQuery("<img>").attr("src", arguments[i]);
  118.         }
  119.     },
  120.     
  121.     /*
  122.      * Set up the preference validation
  123.      */
  124.     setupPrefConstraints: function() {
  125.         // only allow integers for speed limit & port options
  126.         $('div.preference input[@type=text]:not(#download_location)').blur( function() {
  127.             this.value = this.value.replace(/[^0-9]/gi, '');
  128.             if (this.value == '') {
  129.                 if ($(this).is('#refresh_rate')) {
  130.                     this.value = 5;
  131.                 } else {
  132.                     this.value = 0;
  133.                 }
  134.             }
  135.         });
  136.     },
  137.     
  138.     /*
  139.      * Load the clutch prefs and init the GUI according to those prefs
  140.      */
  141.     initializeSettings: function( )
  142.     {
  143.         Prefs.getClutchPrefs( this );
  144.  
  145.         // iPhone conditions in the section allow us to not
  146.         // include transmenu js to save some bandwidth; if we
  147.         // start using prefs on iPhone we need to weed
  148.         // transmenu refs out of that too.
  149.         
  150.         $('#filter_' + this[Prefs._FilterMode] + '_link').parent().addClass('selected');
  151.         
  152.         if (!iPhone) $('#sort_by_' + this[Prefs._SortMethod] ).selectMenuItem();
  153.         
  154.         if (!iPhone && ( this[Prefs._SortDirection] == Prefs._SortDescending ) )
  155.             $('#reverse_sort_order').selectMenuItem();
  156.         
  157.         if( this[Prefs._ShowFilter] )
  158.             this.showFilter( );
  159.         
  160.         if( this[Prefs._ShowInspector] )
  161.             this.showInspector( );
  162.  
  163.     },
  164.  
  165.     /*
  166.      * Set up the search box
  167.      */
  168.     setupSearchBox: function()
  169.     {
  170.         var tr = this;
  171.         var search_box = $('#torrent_search');
  172.         search_box.bind('keyup click', {transmission: this}, function(event) {
  173.             tr.setSearch(this.value);
  174.         });
  175.         if (!$.browser.safari)
  176.         {
  177.             search_box.addClass('blur');
  178.             search_box[0].value = 'Filter';
  179.             search_box.bind('blur', {transmission: this}, function(event) {
  180.                 if (this.value == '') {
  181.                     $(this).addClass('blur');
  182.                     this.value = 'Filter';
  183.                     tr.setSearch(null);
  184.                 }
  185.             }).bind('focus', {}, function(event) {
  186.                 if ($(this).is('.blur')) {
  187.                     this.value = '';
  188.                     $(this).removeClass('blur');
  189.                 }
  190.             });
  191.         }
  192.     },
  193.  
  194.     contextStopSelected: function( ) {
  195.         transmission.stopSelectedTorrents( );
  196.     },
  197.     contextStartSelected: function( ) {
  198.         transmission.startSelectedTorrents( );
  199.     },
  200.     contextRemoveSelected: function( ) {
  201.         transmission.removeSelectedTorrents( );
  202.     },
  203.     contextRemoveDataSelected: function( ) {
  204.         transmission.removeSelectedTorrentsAndData( );
  205.     },
  206.     contextToggleInspector: function( ) {
  207.         transmission.toggleInspector( );
  208.     },
  209.     contextSelectAll: function( ) {
  210.         transmission.selectAll( true );
  211.     },
  212.     contextDeselectAll: function( ) {
  213.         transmission.deselectAll( true );
  214.     },
  215.     
  216.     /*
  217.      * Create the torrent right-click menu
  218.      */
  219.     createContextMenu: function() {
  220.         
  221.         var bindings = {
  222.             context_pause_selected:    this.contextStopSelected,
  223.             context_resume_selected:   this.contextStartSelected,
  224.             context_remove:            this.contextRemoveSelected,
  225.             context_removedata:        this.contextRemoveDataSelected,
  226.             context_toggle_inspector:  this.contextToggleInspector,
  227.             context_select_all:        this.contextSelectAll,
  228.             context_deselect_all:      this.contextDeselectAll
  229.         };
  230.         
  231.         // Setup the context menu
  232.         $('ul#torrent_list').contextMenu('torrent_context_menu', {
  233.             bindings:          bindings,
  234.             menuStyle:         Menu.context.menu_style,
  235.             itemStyle:         Menu.context.item_style,
  236.             itemHoverStyle:    Menu.context.item_hover_style,
  237.             itemDisabledStyle: Menu.context.item_disabled_style,
  238.             shadow:            false,
  239.             boundingElement:   $('div#torrent_container'),
  240.             boundingRightPad:  20,
  241.             boundingBottomPad: 5
  242.         });
  243.     },
  244.     
  245.     /*
  246.      * Create the footer settings menu
  247.      */
  248.     createSettingsMenu: function() {
  249.         $('#settings_menu').transMenu({
  250.             selected_char: '✔',
  251.             direction: 'up',
  252.             onClick: this.processSettingsMenuEvent
  253.         });
  254.         
  255.         $('#unlimited_download_rate').selectMenuItem();
  256.         $('#unlimited_upload_rate').selectMenuItem();
  257.     },
  258.     
  259.  
  260.     /*--------------------------------------------
  261.      * 
  262.      *  U T I L I T I E S
  263.      * 
  264.      *--------------------------------------------*/
  265.  
  266.     getAllTorrents: function()
  267.     {
  268.         return this._torrents;
  269.     },
  270.  
  271.     getVisibleTorrents: function()
  272.     {
  273.         var torrents = [ ];
  274.         for( var i=0, len=this._rows.length; i<len; ++i )
  275.             if( this._rows[i]._torrent )
  276.                 if( this._rows[i][0].style.display != 'none' )
  277.                     torrents.push( this._rows[i]._torrent );
  278.         return torrents;
  279.     },
  280.  
  281.     getSelectedTorrents: function()
  282.     {
  283.         var v = this.getVisibleTorrents( );
  284.         var s = [ ];
  285.         for( var i=0, len=v.length; i<len; ++i )
  286.             if( v[i].isSelected( ) )
  287.                 s.push( v[i] );
  288.         return s;
  289.     },
  290.  
  291.     getVisibleRows: function()
  292.     {
  293.         var rows = [ ];
  294.         for( var i=0, len=this._rows.length; i<len; ++i )
  295.             if( this._rows[i][0].style.display != 'none' )
  296.                 rows.push( this._rows[i] );
  297.         return rows;
  298.     },
  299.  
  300.     getTorrentIndex: function( rows, torrent )
  301.     {
  302.         for( var i=0, len=rows.length; i<len; ++i )
  303.             if( rows[i]._torrent == torrent )
  304.                 return i;
  305.         return null;
  306.     },
  307.  
  308.     setPref: function( key, val )
  309.     {
  310.         this[key] = val;
  311.         Prefs.setValue( key, val );
  312.     },
  313.  
  314.     scrollToElement: function( e )
  315.     {
  316.         if( iPhone )
  317.             return;
  318.  
  319.         var container = $('#torrent_container');
  320.         var scrollTop = container.scrollTop( );
  321.         var innerHeight = container.innerHeight( );
  322.  
  323.         var offsetTop = e[0].offsetTop;
  324.         var offsetHeight = e.outerHeight( );
  325.  
  326.         if( offsetTop < scrollTop )
  327.             container.scrollTop( offsetTop );
  328.         else if( innerHeight + scrollTop < offsetTop + offsetHeight )
  329.             container.scrollTop( offsetTop + offsetHeight - innerHeight );
  330.     },
  331.  
  332.     /*--------------------------------------------
  333.      * 
  334.      *  S E L E C T I O N
  335.      * 
  336.      *--------------------------------------------*/
  337.  
  338.     setSelectedTorrent: function( torrent, doUpdate ) {
  339.         this.deselectAll( );
  340.         this.selectTorrent( torrent, doUpdate );
  341.     },
  342.  
  343.     selectElement: function( e, doUpdate ) {
  344.         $.className.add( e[0], 'selected' );
  345.         this.scrollToElement( e );
  346.         if( doUpdate )
  347.             this.selectionChanged( );
  348.         $.className.add( e[0], 'selected' );
  349.     },
  350.     selectRow: function( rowIndex, doUpdate ) {
  351.         this.selectElement( this._rows[rowIndex], doUpdate );
  352.     },
  353.     selectTorrent: function( torrent, doUpdate ) {
  354.         if( torrent._element )
  355.             this.selectElement( torrent._element, doUpdate );
  356.     },
  357.  
  358.     deselectElement: function( e, doUpdate ) {
  359.         $.className.remove( e[0], 'selected' );
  360.         if( doUpdate )
  361.             this.selectionChanged( );
  362.     },
  363.     deselectTorrent: function( torrent, doUpdate ) {
  364.         if( torrent._element )
  365.             this.deselectElement( torrent._element, doUpdate );
  366.     },
  367.  
  368.     selectAll: function( doUpdate ) {
  369.         var tr = transmission;
  370.         for( var i=0, len=tr._rows.length; i<len; ++i )
  371.             tr.selectElement( tr._rows[i] );
  372.         if( doUpdate )
  373.             tr.selectionChanged();
  374.     },
  375.     deselectAll: function( doUpdate ) {
  376.         var tr = transmission;
  377.         for( var i=0, len=tr._rows.length; i<len; ++i )
  378.             tr.deselectElement( tr._rows[i] );
  379.         tr._last_torrent_clicked = null;
  380.         if( doUpdate )
  381.             tr.selectionChanged( );
  382.     },
  383.  
  384.     /*
  385.      * Select a range from this torrent to the last clicked torrent
  386.      */
  387.     selectRange: function( torrent, doUpdate )
  388.     {
  389.         if( !this._last_torrent_clicked )
  390.         {
  391.             this.selectTorrent( torrent );
  392.         }
  393.         else // select the range between the prevous & current
  394.         {
  395.             var rows = this.getVisibleRows( );
  396.             var i = this.getTorrentIndex( rows, this._last_torrent_clicked );
  397.             var end = this.getTorrentIndex( rows, torrent );
  398.             var step = i < end ? 1 : -1;
  399.             for( ; i!=end; i+=step )
  400.                 this.selectRow( i );
  401.             this.selectRow( i );
  402.         }
  403.  
  404.         if( doUpdate )
  405.             this.selectionChanged( );
  406.     },
  407.     
  408.     selectionChanged: function()
  409.     {
  410.         this.updateButtonStates();
  411.         this.updateInspector();
  412.     },
  413.  
  414.     /*--------------------------------------------
  415.      * 
  416.      *  E V E N T   F U N C T I O N S
  417.      * 
  418.      *--------------------------------------------*/
  419.     
  420.     /*
  421.      * Process key event
  422.      */
  423.     keyDown: function(event)
  424.     {
  425.         var tr = transmission;
  426.         var sel = tr.getSelectedTorrents( );
  427.         var rows = tr.getVisibleRows( );
  428.         var i = -1;
  429.         
  430.         if( event.keyCode == 40 ) // down arrow
  431.         {
  432.             var t = sel.length ? sel[sel.length-1] : null;
  433.             i = t==null ? null : tr.getTorrentIndex(rows,t)+1;
  434.             if( i == rows.length || i == null )
  435.                 i = 0;
  436.         }
  437.         else if( event.keyCode == 38 ) // up arrow
  438.         {
  439.             var t = sel.length ? sel[0] : null
  440.             i = t==null ? null : tr.getTorrentIndex(rows,t)-1;
  441.             if( i == -1 || i == null )
  442.                 i = rows.length - 1;
  443.         }
  444.  
  445.         if( 0<=i && i<rows.length ) {
  446.             tr.deselectAll( );
  447.             tr.selectRow( i, true );
  448.         }
  449.     },
  450.     
  451.     isButtonEnabled: function(e) {
  452.         var p = e.target ? e.target.parentNode : e.srcElement.parentNode;
  453.         return p.className!='disabled' && p.parentNode.className!='disabled';
  454.     },
  455.  
  456.     stopAllClicked: function( event ) {
  457.         var tr = transmission;
  458.         if( tr.isButtonEnabled( event ) ) {
  459.             tr.stopAllTorrents( );
  460.             tr.hideiPhoneAddressbar( );
  461.         }
  462.     },
  463.  
  464.     stopSelectedClicked: function( event ) {
  465.         var tr = transmission;
  466.         if( tr.isButtonEnabled( event ) ) {
  467.             tr.stopSelectedTorrents( );
  468.             tr.hideiPhoneAddressbar( );
  469.         }
  470.     },
  471.  
  472.     startAllClicked: function( event ) {
  473.         var tr = transmission;
  474.         if( tr.isButtonEnabled( event ) ) {
  475.             tr.startAllTorrents( );
  476.             tr.hideiPhoneAddressbar( );
  477.         }
  478.     },
  479.  
  480.     startSelectedClicked: function( event ) {
  481.         var tr = transmission;
  482.         if( tr.isButtonEnabled( event ) ) {
  483.             tr.startSelectedTorrents( );
  484.             tr.hideiPhoneAddressbar( );
  485.         }
  486.     },
  487.  
  488.     openTorrentClicked: function( event ) {
  489.         var tr = transmission;
  490.         if( tr.isButtonEnabled( event ) ) {
  491.             $('body').addClass('open_showing');
  492.             tr.uploadTorrentFile( );
  493.         }
  494.         tr.updateButtonStates();
  495.     },
  496.  
  497.     hideUploadDialog: function( ) {
  498.         $('body.open_showing').removeClass('open_showing');
  499.         if (!iPhone && Safari3) {
  500.             $('div#upload_container div.dialog_window').css('top', '-205px');
  501.             setTimeout("$('#upload_container').hide();",500);
  502.         } else {
  503.             $('#upload_container').hide();
  504.         }
  505.         transmission.updateButtonStates();
  506.     },
  507.  
  508.     cancelUploadClicked: function(event) {
  509.         transmission.hideUploadDialog( );
  510.     },
  511.  
  512.     confirmUploadClicked: function(event) {
  513.         transmission.uploadTorrentFile( true );
  514.         transmission.hideUploadDialog( );
  515.     },
  516.  
  517.     cancelPrefsClicked: function(event) {
  518.         transmission.hidePrefsDialog( );
  519.     },
  520.  
  521.     savePrefsClicked: function(event)
  522.     {
  523.         // handle the clutch prefs locally
  524.         var tr = transmission;
  525.         tr.setPref( Prefs._AutoStart, $('#prefs_form #auto_start')[0].checked );
  526.         var rate = parseInt( $('#prefs_form #refresh_rate')[0].value );
  527.         if( rate != tr[Prefs._RefreshRate] ) {
  528.             tr.setPref( Prefs._RefreshRate, rate );
  529.             tr.togglePeriodicRefresh( false );
  530.             tr.togglePeriodicRefresh( true );
  531.         }
  532.         
  533.         // pass the new prefs upstream to the RPC server
  534.         var o = { };
  535.         o[RPC._PeerPort]         = parseInt( $('#prefs_form #port')[0].value );
  536.         o[RPC._UpSpeedLimit]     = parseInt( $('#prefs_form #upload_rate')[0].value );
  537.         o[RPC._DownSpeedLimit]   = parseInt( $('#prefs_form #download_rate')[0].value );
  538.         o[RPC._DownloadDir]      = $('#prefs_form #download_location')[0].value;
  539.         o[RPC._UpSpeedLimited]   = $('#prefs_form #limit_upload')[0].checked ? 1 : 0;
  540.         o[RPC._DownSpeedLimited] = $('#prefs_form #limit_download')[0].checked ? 1 : 0;
  541.         o[RPC._Encryption]       = $('#prefs_form #encryption')[0].checked
  542.                                        ? RPC._EncryptionRequired
  543.                                        : RPC._EncryptionPreferred;
  544.         tr.remote.savePrefs( o );
  545.         
  546.         tr.hidePrefsDialog( );
  547.     },
  548.  
  549.     removeClicked: function( event ) {    
  550.         var tr = transmission;
  551.         if( tr.isButtonEnabled( event ) ) {
  552.             tr.removeSelectedTorrents( );
  553.             tr.hideiPhoneAddressbar( );
  554.         }
  555.     },
  556.  
  557.     removeDataClicked: function( event ) {    
  558.         var tr = transmission;
  559.         if( tr.isButtonEnabled( event ) ) {
  560.             tr.removeSelectedTorrentsAndData( );
  561.             tr.hideiPhoneAddressbar( );
  562.         }
  563.     },
  564.  
  565.     toggleInspectorClicked: function( event ) {
  566.         var tr = transmission;
  567.         if( tr.isButtonEnabled( event ) )
  568.             tr.toggleInspector( );
  569.     },
  570.  
  571.     inspectorTabClicked: function(event) {
  572.     
  573.         if (iPhone) event.stopPropagation();
  574.         
  575.         // Select the clicked tab, unselect the others,
  576.         // and display the appropriate info
  577.         var tab_ids = ['inspector_tab_info', 'inspector_tab_activity'];
  578.         for( var i=0; i<tab_ids.length; ++i ) {
  579.             if (this.id == tab_ids[i]) {
  580.                 $('#' + tab_ids[i]).addClass('selected');
  581.                 $('#' + tab_ids[i] + '_container').show();
  582.             } else {
  583.                 $('#' + tab_ids[i]).removeClass('selected');
  584.                 $('#' + tab_ids[i] + '_container').hide();
  585.             }
  586.         }
  587.         transmission.hideiPhoneAddressbar();
  588.     },
  589.     
  590.     toggleFilterClicked: function(event) {
  591.         if (transmission.isButtonEnabled(event))
  592.             transmission.toggleFilter();
  593.     },
  594.     setFilter: function( mode )
  595.     {
  596.         // update the radiobuttons
  597.         var c;
  598.         switch( mode ) {
  599.             case Prefs._FilterAll:         c = '#filter_all_link'; break;
  600.             case Prefs._FilterSeeding:     c = '#filter_seeding_link'; break;
  601.             case Prefs._FilterDownloading: c = '#filter_downloading_link'; break;
  602.             case Prefs._FilterPaused:      c = '#filter_paused_link'; break;
  603.         }
  604.         $(c).parent().siblings().removeClass('selected');
  605.         $(c).parent().addClass('selected');
  606.  
  607.         // do the filtering
  608.         this.setPref( Prefs._FilterMode, mode );
  609.         this.refilter( );
  610.     },
  611.     showAllClicked: function( event ) {    
  612.         transmission.setFilter( Prefs._FilterAll );
  613.     },
  614.     showDownloadingClicked: function( event ) {
  615.         transmission.setFilter( Prefs._FilterDownloading );
  616.     },
  617.     showSeedingClicked: function(event) {    
  618.         transmission.setFilter( Prefs._FilterSeeding );
  619.     },
  620.     showPausedClicked: function(event) {
  621.         transmission.setFilter( Prefs._FilterPaused );
  622.     },
  623.  
  624.     /*
  625.      * 'Clutch Preferences' was clicked (iPhone only)
  626.      */
  627.     releaseClutchPreferencesButton: function(event) {
  628.         $('div#prefs_container div#pref_error').hide();
  629.         $('div#prefs_container h2.dialog_heading').show();
  630.         this.showPrefsDialog( );
  631.     },
  632.  
  633.     /*
  634.      * Turn the periodic ajax-refresh on & off
  635.      */
  636.     togglePeriodicRefresh: function(state) {
  637.         if (state && this._periodic_refresh == null) {
  638.             // sanity check
  639.             if( !this[Prefs._RefreshRate] )
  640.                  this[Prefs._RefreshRate] = 5;
  641.             this._periodic_refresh = setInterval('transmission.remote.loadTorrents()', this[Prefs._RefreshRate] * 1000 );
  642.         } else {
  643.             clearInterval(this._periodic_refresh);
  644.             this._periodic_refresh = null;
  645.         }
  646.     },
  647.  
  648.     /*--------------------------------------------
  649.      * 
  650.      *  I N T E R F A C E   F U N C T I O N S
  651.      * 
  652.      *--------------------------------------------*/
  653.     
  654.     showPrefsDialog: function( )
  655.     {
  656.         $('body').addClass('prefs_showing');
  657.         $('#prefs_container').show();
  658.         transmission.hideiPhoneAddressbar();
  659.         if( Safari3 )
  660.             setTimeout("$('div#prefs_container div.dialog_window').css('top', '0px');",10);
  661.         this.updateButtonStates( );
  662.     },
  663.  
  664.     hidePrefsDialog: function( )
  665.     {
  666.         $('body.prefs_showing').removeClass('prefs_showing');
  667.         if (iPhone) {
  668.             transmission.hideiPhoneAddressbar();
  669.             $('#prefs_container').hide();
  670.         } else if (Safari3) {
  671.             $('div#prefs_container div.dialog_window').css('top', '-425px');
  672.             setTimeout("$('#prefs_container').hide();",500);
  673.         } else {
  674.             $('#prefs_container').hide();
  675.         }
  676.         this.updateButtonStates( );
  677.     },
  678.     
  679.     /*
  680.      * Process got some new session data from the server
  681.      */
  682.     updatePrefs: function( prefs )
  683.     {
  684.         // remember them for later
  685.         this._prefs = prefs;
  686.  
  687.         var down_limit    = prefs[RPC._DownSpeedLimit];
  688.         var down_limited  = prefs[RPC._DownSpeedLimited];
  689.         var up_limit      = prefs[RPC._UpSpeedLimit];
  690.         var up_limited    = prefs[RPC._UpSpeedLimited];
  691.         
  692.         $('div.download_location input')[0].value = prefs['download-dir'];
  693.         $('div.port input')[0].value              = prefs['port'];
  694.         $('div.auto_start input')[0].checked      = prefs[Prefs._AutoStart];
  695.         $('input#limit_download')[0].checked      = down_limited == 1;
  696.         $('input#download_rate')[0].value         = down_limit;
  697.         $('input#limit_upload')[0].checked        = up_limited == 1;
  698.         $('input#upload_rate')[0].value           = up_limit;
  699.         $('input#refresh_rate')[0].value          = prefs[Prefs._RefreshRate];
  700.         $('div.encryption input')[0].checked      = prefs[RPC._Encryption] == RPC._EncryptionRequired;
  701.  
  702.         if (!iPhone)
  703.         {
  704.             setInnerHTML( $('#limited_download_rate')[0], 'Limit (' + down_limit + ' KB/s)' );
  705.             var key = down_limited ? '#limited_download_rate'
  706.                                    : '#unlimited_download_rate';
  707.             $(key).deselectMenuSiblings().selectMenuItem();
  708.         
  709.             setInnerHTML( $('#limited_upload_rate')[0], 'Limit (' + up_limit + ' KB/s)' );
  710.             key = up_limited ? '#limited_upload_rate'
  711.                              : '#unlimited_upload_rate';
  712.             $(key).deselectMenuSiblings().selectMenuItem();
  713.         }
  714.     },
  715.     
  716.     setSearch: function( search ) {
  717.         this._current_search = search ? search.trim() : null;
  718.         this.refilter( );
  719.     },
  720.  
  721.     setSortMethod: function( sort_method ) {
  722.         this.setPref( Prefs._SortMethod, sort_method );
  723.         this.refilter( );
  724.     },
  725.  
  726.     setSortDirection: function( direction ) {
  727.         this.setPref( Prefs._SortDirection, direction );
  728.         this.refilter( );
  729.     },
  730.  
  731.     /*
  732.      * Process an event in the footer-menu
  733.      */
  734.     processSettingsMenuEvent: function(event) {
  735.         // Don't use 'this' in the function to avoid confusion (this != transmission instance)
  736.         var element = this;
  737.         
  738.         // Figure out which menu has been clicked
  739.         switch ($(element).parent()[0].id) {
  740.             
  741.             // Display the preferences dialog
  742.             case 'footer_super_menu':
  743.                 if ($(element)[0].id == 'preferences') {
  744.                     $('div#prefs_container div#pref_error').hide();
  745.                     $('div#prefs_container h2.dialog_heading').show();
  746.                     transmission.showPrefsDialog( );
  747.                 }
  748.                 break;
  749.             
  750.             // Limit the download rate
  751.             case 'footer_download_rate_menu':
  752.                 var args = { };
  753.                 var rate = (this.innerHTML).replace(/[^0-9]/ig, '');
  754.                 if ($(this).is('#unlimited_download_rate')) {
  755.                     $(this).deselectMenuSiblings().selectMenuItem();
  756.                     args[RPC._DownSpeedLimited] = false;
  757.                 } else {
  758.                     setInnerHTML( $('#limited_download_rate')[0], 'Limit (' + rate + ' KB/s)' );
  759.                     $('#limited_download_rate').deselectMenuSiblings().selectMenuItem();
  760.                     $('div.preference input#download_rate')[0].value = rate;
  761.                     args[RPC._DownSpeedLimit] = parseInt( rate );
  762.                     args[RPC._DownSpeedLimited] = true;
  763.                 }
  764.                 $('div.preference input#limit_download')[0].checked = args[RPC._DownSpeedLimited];
  765.                 transmission.remote.savePrefs( args );
  766.                 break;
  767.             
  768.             // Limit the upload rate
  769.             case 'footer_upload_rate_menu':
  770.                 var args = { };
  771.                 var rate = (this.innerHTML).replace(/[^0-9]/ig, '');
  772.                 if ($(this).is('#unlimited_upload_rate')) {
  773.                     $(this).deselectMenuSiblings().selectMenuItem();
  774.                     args[RPC._UpSpeedLimited] = false;
  775.                 } else {
  776.                     setInnerHTML( $('#limited_upload_rate')[0], 'Limit (' + rate + ' KB/s)' );
  777.                     $('#limited_upload_rate').deselectMenuSiblings().selectMenuItem();
  778.                     $('div.preference input#upload_rate')[0].value = rate;
  779.                     args[RPC._UpSpeedLimit] = parseInt( rate );
  780.                     args[RPC._UpSpeedLimited] = true;
  781.                 }
  782.                 $('div.preference input#limit_upload')[0].checked = args[RPC._UpSpeedLimited];
  783.                 transmission.remote.savePrefs( args );
  784.                 break;
  785.             
  786.             // Sort the torrent list 
  787.             case 'footer_sort_menu':
  788.  
  789.                 // The 'reverse sort' option state can be toggled independently of the other options
  790.                 if ($(this).is('#reverse_sort_order')) {
  791.                     var dir;
  792.                     if ($(this).menuItemIsSelected()) {
  793.                         $(this).deselectMenuItem();
  794.                         dir = Prefs._SortAscending;
  795.                     } else {
  796.                         $(this).selectMenuItem();
  797.                         dir = Prefs._SortDescending;
  798.                     }
  799.                     transmission.setSortDirection( dir );
  800.  
  801.                 // Otherwise, deselect all other options (except reverse-sort) and select this one
  802.                 } else {
  803.                     $(this).parent().find('span.selected').each( function() {
  804.                         if (! $(this).parent().is('#reverse_sort_order')) {
  805.                             $(this).parent().deselectMenuItem();
  806.                         }
  807.                     });
  808.                     $(this).selectMenuItem();
  809.                     var method = $(this)[0].id.replace(/sort_by_/, '');
  810.                     transmission.setSortMethod( method );
  811.                 }
  812.                 break;
  813.         }
  814.     },
  815.  
  816.     setLastTorrentClicked: function( torrent )
  817.     {
  818.         this._last_torrent_clicked = torrent;
  819.     },
  820.     
  821.     /*
  822.      * Update the inspector with the latest data for the selected torrents
  823.      */
  824.     updateInspector: function()
  825.     {
  826.         if( !this[Prefs._ShowInspector] )
  827.             return;
  828.  
  829.         var torrents = this.getSelectedTorrents( );
  830.         if( !torrents.length && iPhone ) {
  831.             transmission.hideInspector();
  832.             return;
  833.         }
  834.  
  835.         var creator = 'N/A';
  836.         var comment = 'N/A';
  837.         var date_created = 'N/A';
  838.         var error = '';
  839.         var hash = 'N/A';
  840.         var have_public = false;
  841.         var have_private = false;
  842.         var name;
  843.         var sizeWhenDone = 0;
  844.         var sizeDone = 0;
  845.         var total_completed = 0;
  846.         var total_download = 0;
  847.         var total_download_peers = 0;
  848.         var total_download_speed = 0;
  849.         var total_have = 0;
  850.         var total_leechers = 0;
  851.         var total_size = 0;
  852.         var total_seeders = 0;
  853.         var total_state = null;
  854.         var total_swarm_speed = 0;
  855.         var total_tracker = null;
  856.         var total_upload = 0;
  857.         var total_upload_peers = 0;
  858.         var total_upload_speed = 0;
  859.         var total_verified = 0;
  860.         var na = 'N/A';
  861.         
  862.         $("#torrent_inspector_size, .inspector_row div").css('color', '#222');
  863.  
  864.         if( torrents.length == 0 )
  865.         {
  866.             var ti = '#torrent_inspector_';
  867.             setInnerHTML( $(ti+'name')[0], 'No Selection' );
  868.             setInnerHTML( $(ti+'size')[0], na );
  869.             setInnerHTML( $(ti+'tracker')[0], na );
  870.             setInnerHTML( $(ti+'hash')[0], na );
  871.             setInnerHTML( $(ti+'state')[0], na );
  872.             setInnerHTML( $(ti+'download_speed')[0], na );
  873.             setInnerHTML( $(ti+'upload_speed')[0], na );
  874.             setInnerHTML( $(ti+'uploaded')[0], na );
  875.             setInnerHTML( $(ti+'downloaded')[0], na );
  876.             setInnerHTML( $(ti+'ratio')[0], na );
  877.             setInnerHTML( $(ti+'total_seeders')[0], na );
  878.             setInnerHTML( $(ti+'total_leechers')[0], na );
  879.             setInnerHTML( $(ti+'swarm_speed')[0], na );
  880.             setInnerHTML( $(ti+'have')[0], na );
  881.             setInnerHTML( $(ti+'upload_to')[0], na );
  882.             setInnerHTML( $(ti+'download_from')[0], na );
  883.             setInnerHTML( $(ti+'secure')[0], na );
  884.             setInnerHTML( $(ti+'creator_date')[0], na );
  885.             setInnerHTML( $(ti+'progress')[0], na );
  886.             setInnerHTML( $(ti+'comment')[0], na );
  887.             setInnerHTML( $(ti+'creator')[0], na );
  888.             setInnerHTML( $(ti+'error')[0], na );        
  889.             $("#torrent_inspector_size, .inspector_row > div:contains('N/A')").css('color', '#666');
  890.             return;
  891.         }
  892.  
  893.         name = torrents.length == 1
  894.             ? torrents[0].name()
  895.             : torrents.length+' Transfers Selected';
  896.  
  897.         if( torrents.length == 1 )
  898.         {
  899.             var t = torrents[0];
  900.             if( t._error_message )
  901.             {
  902.                 error = t._error_message ;
  903.             }
  904.             if( t._comment)
  905.             {
  906.                 comment = t._comment ;
  907.             }
  908.             if( t._creator )
  909.             {
  910.                 creator = t._creator ;
  911.             }
  912.             hash = t.hash();
  913.             date_created = Math.formatTimestamp( t._creator_date );
  914.         }
  915.  
  916.         for( i=0; i<torrents.length; ++i ) {
  917.             var t = torrents[i];
  918.             sizeWhenDone         += t._sizeWhenDone;
  919.             sizeDone             += t._sizeWhenDone - t._leftUntilDone;
  920.             total_completed      += t.completed();
  921.             total_verified       += t._verified;
  922.             total_size           += t.size();
  923.             total_upload         += t.uploadTotal();
  924.             total_download       += t.downloadTotal();
  925.             total_upload_speed   += t.uploadSpeed();
  926.             total_download_speed += t.downloadSpeed();
  927.             total_seeders        += t.totalSeeders();
  928.             total_leechers       += t.totalLeechers();
  929.             total_upload_peers   += t.peersGettingFromUs();
  930.             total_download_peers += t.peersSendingToUs();
  931.             total_swarm_speed    += t.swarmSpeed();
  932.             if( total_state == null )
  933.                 total_state = t.stateStr();
  934.             else if ( total_state.search ( t.stateStr() ) == -1 )
  935.                 total_state += '/' + t.stateStr();
  936.             var tracker = t._tracker;
  937.             if( total_tracker == null )
  938.                 total_tracker = tracker;
  939.             else if ( total_tracker.search ( tracker ) == -1 )  
  940.                 total_tracker += ', ' + tracker;
  941.             if( t._is_private )
  942.                 have_private = true;
  943.             else
  944.                 have_public = true;
  945.         }
  946.  
  947.         var private_string = '';
  948.         if( have_private && have_public ) private_string = 'Mixed';
  949.         else if( have_private ) private_string = 'Private Torrent';
  950.         else if( have_public ) private_string = 'Public Torrent';    
  951.  
  952.         var ti = '#torrent_inspector_';
  953.         $(ti+'name')[0].innerHTML            = name;
  954.         $(ti+'size')[0].innerHTML            = torrents.length ? Math.formatBytes( total_size ) : 'N/A';
  955.         $(ti+'tracker')[0].innerHTML         = total_tracker;
  956.         $(ti+'hash')[0].innerHTML            = hash;
  957.         $(ti+'state')[0].innerHTML           = total_state;
  958.         $(ti+'download_speed')[0].innerHTML  = torrents.length ? Math.formatBytes( total_download_speed ) + '/s' : 'N/A';
  959.         $(ti+'upload_speed')[0].innerHTML    = torrents.length ? Math.formatBytes( total_upload_speed ) + '/s' : 'N/A';
  960.         $(ti+'uploaded')[0].innerHTML        = torrents.length ? Math.formatBytes( total_upload ) : 'N/A';
  961.         $(ti+'downloaded')[0].innerHTML      = torrents.length ? Math.formatBytes( total_download ) : 'N/A';
  962.         $(ti+'ratio')[0].innerHTML           = torrents.length ? Math.ratio( total_upload, total_download ) : 'N/A';
  963.         $(ti+'total_seeders')[0].innerHTML   = torrents.length ? total_seeders : 'N/A';
  964.         $(ti+'total_leechers')[0].innerHTML  = torrents.length ? total_leechers : 'N/A';
  965.         $(ti+'swarm_speed')[0].innerHTML     = torrents.length ? Math.formatBytes(total_swarm_speed) + '/s' : 'N/A';
  966.         $(ti+'have')[0].innerHTML            = torrents.length ? Math.formatBytes(total_completed) + ' (' + Math.formatBytes(total_verified) + ' verified)' : 'N/A';
  967.         $(ti+'upload_to')[0].innerHTML       = torrents.length ? total_upload_peers : 'N/A';
  968.         $(ti+'download_from')[0].innerHTML   = torrents.length ? total_download_peers : 'N/A';
  969.         $(ti+'secure')[0].innerHTML          = private_string;
  970.         $(ti+'creator_date')[0].innerHTML    = date_created;
  971.         $(ti+'progress')[0].innerHTML        = torrents.length ? Math.ratio( sizeDone*100, sizeWhenDone ) + '%' : 'N/A';
  972.         $(ti+'comment')[0].innerHTML         = comment;
  973.         $(ti+'creator')[0].innerHTML         = creator;
  974.         $(ti+'error')[0].innerHTML           = error;
  975.         
  976.         $(".inspector_row > div:contains('N/A')").css('color', '#666');
  977.     },
  978.     
  979.     /*
  980.      * Toggle the visibility of the inspector (used by the context menu)
  981.      */
  982.     toggleInspector: function() {
  983.         if( this[Prefs._ShowInspector] )
  984.             this.hideInspector( );
  985.         else
  986.             this.showInspector( );
  987.     },
  988.     
  989.     showInspector: function() {
  990.         $('#torrent_inspector').show();
  991.         if (iPhone) {
  992.             $('body').addClass('inspector_showing');
  993.             this.hideiPhoneAddressbar();
  994.         } else {
  995.             var w = $('#torrent_inspector').width() + 1 + 'px';
  996.             $('#torrent_filter_bar')[0].style.right = w;
  997.             $('#torrent_container')[0].style.right = w;
  998.         }
  999.  
  1000.         setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Hide Inspector' );
  1001.  
  1002.         this.setPref( Prefs._ShowInspector, true );
  1003.         this.updateInspector( );
  1004.     },
  1005.     
  1006.     /*
  1007.      * Hide the inspector
  1008.      */
  1009.     hideInspector: function() {
  1010.  
  1011.         $('#torrent_inspector').hide();
  1012.         
  1013.         if (iPhone) {
  1014.             transmsision.deselectAll( );
  1015.             $('body.inspector_showing').removeClass('inspector_showing');
  1016.             transmission.hideiPhoneAddressbar();
  1017.         } else {
  1018.             $('#torrent_filter_bar')[0].style.right = '0px';
  1019.             $('#torrent_container')[0].style.right = '0px';
  1020.             setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Show Inspector' );
  1021.         }
  1022.         
  1023.         this.setPref( Prefs._ShowInspector, false );
  1024.     },
  1025.     
  1026.     /*
  1027.      * Toggle the visibility of the filter bar
  1028.      */
  1029.     toggleFilter: function() {
  1030.         if( this[Prefs._ShowFilter] )
  1031.             this.hideFilter();
  1032.         else
  1033.             this.showFilter();
  1034.     },
  1035.  
  1036.     showFilter: function( ) {
  1037.         var container_top = parseInt($('#torrent_container').position().top) + $('#torrent_filter_bar').height() + 1;
  1038.         $('#torrent_container').css('top', container_top + 'px');
  1039.         $('#torrent_filter_bar').show();
  1040.         this.setPref( Prefs._ShowFilter, true );
  1041.     },
  1042.     
  1043.     hideFilter: function()
  1044.     {
  1045.         var container_top = parseInt($('#torrent_container').css('top')) - $('#torrent_filter_bar').height() - 1;
  1046.         $('#torrent_container').css('top', container_top + 'px');
  1047.         $('#torrent_filter_bar').hide();
  1048.         this.setPref( Prefs._ShowFilter, false );
  1049.         this.setFilter( Prefs._FilterAll );
  1050.     },
  1051.  
  1052.     /*
  1053.      * Process got some new torrent data from the server
  1054.      */
  1055.     updateTorrents: function( torrent_list )
  1056.     {
  1057.         var torrent_data;
  1058.         var new_torrents = [];
  1059.         var torrent_ids = [];
  1060.         var handled = [];
  1061.         
  1062.         // refresh existing torrents
  1063.         for( var i=0, len=torrent_list.length; i<len; ++i ) {
  1064.             var data = torrent_list[i];
  1065.             var t = Torrent.lookup( this._torrents, data.id );
  1066.             if( !t )
  1067.                 new_torrents.push( data );
  1068.             else {
  1069.                 t.refresh( data );
  1070.                 handled.push( t );
  1071.             }
  1072.         }
  1073.         
  1074.         // Add any torrents that aren't already being displayed
  1075.         if( new_torrents.length ) {
  1076.             for( var i=0, len=new_torrents.length; i<len; ++i ) {
  1077.                 var t = new Torrent( this, new_torrents[i] );
  1078.                 this._torrents.push( t );
  1079.                 handled.push( t );
  1080.             }
  1081.             this._torrents.sort( Torrent.compareById ); 
  1082.         }
  1083.         
  1084.         // Remove any torrents that weren't in the refresh list
  1085.         var removedAny = false;
  1086.         handled.sort( Torrent.compareById ); // for Torrent.indexOf
  1087.         var allTorrents = this._torrents.clone();
  1088.         for( var i=0, len=allTorrents.length; i<len; ++i ) {
  1089.             var t = allTorrents[i];
  1090.             if( Torrent.indexOf( handled, t.id() ) == -1 ) {
  1091.                 var pos = Torrent.indexOf( this._torrents, t.id( ) );
  1092.                 var e = this._torrents[pos].element();
  1093.                 if( e ) {
  1094.                     delete e._torrent;
  1095.                     e.hide( );
  1096.                 }
  1097.                 this._torrents.splice( pos, 1 );
  1098.                 removedAny = true;
  1099.             }
  1100.         }
  1101.         
  1102.         if( ( new_torrents.length != 0 ) || removedAny ) {
  1103.             this.hideiPhoneAddressbar();
  1104.             this.deselectAll( true );
  1105.         }
  1106.         
  1107.         // FIXME: not sure if this is possible in RPC
  1108.         // Update the disk space remaining
  1109.         //var disk_space_msg = 'Free Space: '
  1110.         //+ Math.formatBytes(data.free_space_bytes)
  1111.         //+ ' (' + data.free_space_percent + '% )';
  1112.         //setInnerHTML( $('div#disk_space_container')[0], disk_space_msg );
  1113.         
  1114.         this.refilter( );
  1115.     },
  1116.  
  1117.     /*
  1118.      * Set the alternating background colors for torrents
  1119.      */
  1120.     setTorrentBgColors: function( )
  1121.     {
  1122.         var rows = this.getVisibleRows( );
  1123.         for( var i=0, len=rows.length; i<len; ++i )
  1124.             if ((i+1) % 2 == 0)
  1125.                 $.className.add( rows[i][0], 'even' );
  1126.             else
  1127.                 $.className.remove( rows[i][0], 'even' );
  1128.     },
  1129.     
  1130.     updateStatusbar: function()
  1131.     {
  1132.         var torrents = this.getAllTorrents();
  1133.         var torrentCount = torrents.length;
  1134.         var visibleCount = this.getVisibleTorrents().length;
  1135.         
  1136.         // calculate the overall speed
  1137.         var upSpeed = 0;
  1138.         var downSpeed = 0;
  1139.         for( var i=0; i<torrentCount; ++i ) {
  1140.             upSpeed += torrents[i].uploadSpeed( );
  1141.             downSpeed += torrents[i].downloadSpeed( );
  1142.         }
  1143.         
  1144.         // update torrent count label
  1145.         var s;
  1146.         if( torrentCount == visibleCount )
  1147.             s = torrentCount + ' Transfers';
  1148.         else
  1149.             s = visibleCount + ' of ' + torrentCount + ' Transfers';
  1150.         setInnerHTML( $('#torrent_global_transfer')[0], s );
  1151.         
  1152.         // update the speeds
  1153.         s = Math.formatBytes( upSpeed ) + '/s';
  1154.         if( iPhone ) s = 'UL: ' + s;
  1155.         setInnerHTML( $('#torrent_global_upload')[0], s );
  1156.         
  1157.         // download speeds
  1158.         s = Math.formatBytes( downSpeed ) + '/s';
  1159.         if( iPhone ) s = 'DL: ' + s;
  1160.         setInnerHTML( $('#torrent_global_download')[0], s );
  1161.     },
  1162.     
  1163.     /*
  1164.      * Select a torrent file to upload
  1165.      * FIXME
  1166.      */
  1167.     uploadTorrentFile: function(confirmed)
  1168.     {
  1169.         // Display the upload dialog
  1170.         if (! confirmed) {
  1171.                 $('input#torrent_upload_file').attr('value', '');
  1172.                 $('input#torrent_upload_url').attr('value', ''); 
  1173.                 $('#upload_container').show();
  1174.             if (!iPhone && Safari3) {
  1175.                 setTimeout("$('div#upload_container div.dialog_window').css('top', '0px');",10);
  1176.             }
  1177.             
  1178.         // Submit the upload form
  1179.         } else {
  1180.             var tr = this;
  1181.             var args = { };
  1182.             if ('' != $('#torrent_upload_url').val()) {
  1183.                 tr.remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: !this[Prefs._Autostart] });
  1184.             } else {
  1185.                 args.url = '/transmission/upload?paused=' + (this[Prefs._AutoStart] ? 'false' : 'true');
  1186.                 args.type = 'POST';
  1187.                 args.dataType = 'xml';
  1188.                 args.iframe = true;
  1189.                 args.success = function( data ) {
  1190.                     tr.remote.loadTorrents( );
  1191.                     tr.togglePeriodicRefresh( true );
  1192.                 };
  1193.                 this.togglePeriodicRefresh( false );
  1194.                 $('#torrent_upload_form').ajaxSubmit( args );
  1195.             }
  1196.         }
  1197.     },
  1198.    
  1199.     removeSelectedTorrents: function() {
  1200.         var torrents = this.getSelectedTorrents( );
  1201.         if( torrents.length )
  1202.             this.promptToRemoveTorrents( torrents );
  1203.     },
  1204.  
  1205.     removeSelectedTorrentsAndData: function() {
  1206.         var torrents = this.getSelectedTorrents( );
  1207.         if( torrents.length )
  1208.             this.promptToRemoveTorrentsAndData( torrents );
  1209.     },
  1210.  
  1211.     promptToRemoveTorrents:function( torrents )
  1212.     {
  1213.         if( torrents.length == 1 )
  1214.         {
  1215.             var torrent = torrents[0];
  1216.             var header = 'Remove ' + torrent.name() + '?';
  1217.             var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
  1218.             dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
  1219.         }
  1220.         else 
  1221.         {
  1222.             var header = 'Remove ' + torrents.length + ' transfers?';
  1223.             var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
  1224.             dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
  1225.         }
  1226.     },
  1227.  
  1228.     promptToRemoveTorrentsAndData:function( torrents )
  1229.     {
  1230.         if( torrents.length == 1 )
  1231.         {
  1232.             var torrent = torrents[0],
  1233.                 header = 'Remove ' + torrent.name() + ' and delete data?',
  1234.                 message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
  1235.             dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
  1236.         }
  1237.         else 
  1238.         {
  1239.             var header = 'Remove ' + torrents.length + ' transfers and delete data?',
  1240.                 message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
  1241.             dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
  1242.         }
  1243.     },
  1244.  
  1245.     removeTorrents: function( torrents ) {
  1246.         this.remote.removeTorrents( torrents );
  1247.     },
  1248.  
  1249.     removeTorrentsAndData: function( torrents ) {
  1250.         this.remote.removeTorrentsAndData( torrents );
  1251.     },
  1252.  
  1253.     startSelectedTorrents: function( ) {
  1254.         this.startTorrents( this.getSelectedTorrents( ) );
  1255.     },
  1256.     startAllTorrents: function( ) {
  1257.         this.startTorrents( this.getAllTorrents( ) );
  1258.     },
  1259.     startTorrent: function( torrent ) {
  1260.         this.startTorrents( [ torrent ] );
  1261.     },
  1262.     startTorrents: function( torrents ) {
  1263.         this.remote.startTorrents( torrents );
  1264.     },
  1265.     
  1266.     stopSelectedTorrents: function( ) {
  1267.         this.stopTorrents( this.getSelectedTorrents( ) );
  1268.     },
  1269.     stopAllTorrents: function( ) {
  1270.         this.stopTorrents( this.getAllTorrents( ) );
  1271.     },
  1272.     stopTorrent: function( torrent ) {
  1273.         this.stopTorrents( [ torrent ] );
  1274.     },
  1275.     stopTorrents: function( torrents ) {
  1276.         this.remote.stopTorrents( torrents );
  1277.     },
  1278.     
  1279.     hideiPhoneAddressbar: function(timeInSeconds) {
  1280.         if( iPhone ) {
  1281.             var delayLength = timeInSeconds ? timeInSeconds*1000 : 150;
  1282.             // not currently supported on iPhone
  1283.             if(/*document.body.scrollTop!=1 && */scroll_timeout==null) {
  1284.                 scroll_timeout = setTimeout("transmission.doToolbarHide()", delayLength);
  1285.             }
  1286.         }
  1287.     },
  1288.     doToolbarHide: function() {
  1289.         window.scrollTo(0,1);
  1290.         scroll_timeout=null;
  1291.     },
  1292.  
  1293.     /***
  1294.     ****
  1295.     ***/
  1296.  
  1297.     refilter: function()
  1298.     {
  1299.         // decide which torrents to keep showing
  1300.         var allTorrents = this.getAllTorrents( );
  1301.         var keep = [ ];
  1302.         for( var i=0, len=allTorrents.length; i<len; ++i ) {
  1303.             var t = allTorrents[i];
  1304.             if( t.test( this[Prefs._FilterMode], this._current_search ) )
  1305.                 keep.push( t );
  1306.         }
  1307.  
  1308.         // sort the keepers
  1309.         Torrent.sortTorrents( keep, this[Prefs._SortMethod],
  1310.                                     this[Prefs._SortDirection] );
  1311.  
  1312.         // make a backup of the selection
  1313.         var sel = this.getSelectedTorrents( );
  1314.         this.deselectAll( );
  1315.  
  1316.         // hide the ones we're not keeping
  1317.         for( var i=keep.length; i<this._rows.length; ++i ) {
  1318.             var e = this._rows[i];
  1319.             delete e._torrent;
  1320.             e[0].style.display = 'none';
  1321.         }
  1322.  
  1323.         // show the ones we're keeping
  1324.         sel.sort( Torrent.compareById );
  1325.         for( var i=0, len=keep.length; i<len; ++i ) {
  1326.             var e = this._rows[i];
  1327.             e[0].style.display = 'block';
  1328.             var t = keep[i];
  1329.             t.setElement( e );
  1330.             if( Torrent.indexOf( sel, t.id() ) != -1 )
  1331.                 this.selectElement( e );
  1332.         }
  1333.  
  1334.         // sync gui
  1335.         this.setTorrentBgColors( );
  1336.         this.updateStatusbar( );
  1337.         this.selectionChanged( );
  1338.     },
  1339.  
  1340.     setEnabled: function( key, flag )
  1341.     {
  1342.         if( flag )
  1343.             $(key + '.disabled').removeClass('disabled');
  1344.         else
  1345.             $(key).addClass('disabled');
  1346.     },
  1347.  
  1348.     updateButtonStates: function()
  1349.     {
  1350.         var showing_dialog = new RegExp("(prefs_showing|dialog_showing|open_showing)").test(document.body.className);
  1351.         if (showing_dialog)
  1352.         {
  1353.             $('.torrent_global_menu ul li').addClass('disabled');
  1354.         }
  1355.         else
  1356.         {
  1357.             var torrents = this.getVisibleTorrents( );
  1358.             var haveSelection = false;
  1359.             var haveActive = false;
  1360.             var haveActiveSelection = false;
  1361.             var havePaused = false;
  1362.             var havePausedSelection = false;
  1363.  
  1364.             for( var i=0, len=torrents.length; !haveSelection && i<len; ++i ) {
  1365.                 var isActive = torrents[i].isActive( );
  1366.                 var isSelected = torrents[i].isSelected( );
  1367.                 if( isActive ) haveActive = true;
  1368.                 if( !isActive ) havePaused = true;
  1369.                 if( isSelected ) haveSelection = true;
  1370.                 if( isSelected && isActive ) haveActiveSelection = true;
  1371.                 if( isSelected && !isActive ) havePausedSelection = true;
  1372.             }
  1373.  
  1374.             $('.torrent_global_menu ul li.disabled').removeClass('disabled');
  1375.  
  1376.             this.setEnabled( 'li#pause_selected', haveActiveSelection );
  1377.             this.setEnabled( 'li.context_pause_selected', haveActiveSelection );
  1378.             this.setEnabled( 'li#resume_selected', havePausedSelection );
  1379.             this.setEnabled( 'li.context_resume_selected', havePausedSelection );
  1380.             this.setEnabled( 'li#remove', haveSelection );
  1381.             this.setEnabled( 'li#removedata', haveSelection );
  1382.             this.setEnabled( 'li#pause_all', haveActive );
  1383.             this.setEnabled( 'li#resume_all', havePaused );
  1384.         }
  1385.     }
  1386. };
  1387.